/**
 * TODO:
 *  我想要的实现方法是什么？
 *  创建一个Rain实例
 *  动态修改updateMaxHeight的方法
 *  有一个循环渲染的方法
 *
 *
 * FIXME:
 *  在房屋类模型的内部时，仍然会看到雨滴穿过，需要改进
 * 关键点:要实现雨滴的碰撞检测
 *  - 将物品放置于1层？
 *    实测对于房屋fbx无效
 */
import * as THREE from 'three';

class DepthData extends THREE.WebGLRenderTarget {
  constructor(size, camParams) {
    super(size, size);
    this.texture.minFilter = THREE.NearestFilter;
    this.texture.magFilter = THREE.NearestFilter;
    this.stencilBuffer = false;
    this.depthTexture = new THREE.DepthTexture();
    this.depthTexture.format = THREE.DepthFormat;
    this.depthTexture.type = THREE.UnsignedIntType;

    let hw = camParams.width * 0.5;
    let hh = camParams.height * 0.5;
    let d = camParams.depth;
    this.depthCam = new THREE.OrthographicCamera(-hw, hw, hh, -hh, 0, d);
    this.depthCam.layers.set(1);
    this.depthCam.position.set(0, d, 0);
    this.depthCam.lookAt(0, 0, 0);
  }

  update(renderOption) {
    const { renderer, scene } = renderOption;
    renderer.setRenderTarget(this);
    renderer.render(scene, this.depthCam);
    renderer.setRenderTarget(null);
  }
}

export class Rain extends THREE.Line {
  constructor(size, amount, uniforms) {
    let v = new THREE.Vector3();
    let gBase = new THREE.BufferGeometry().setFromPoints([new THREE.Vector2(0, 0), new THREE.Vector2(0, 1)]);
    const geometry = new THREE.InstancedBufferGeometry().copy(gBase);

    // 生成初始粒子位置
    const particlePositions = Array.from({ length: amount }, () => {
      v.random().subScalar(0.5);
      v.y += 0.5;
      v.multiply(size);
      return [...v];
    });

    geometry.setAttribute('instPos', new THREE.InstancedBufferAttribute(new Float32Array(particlePositions.flat()), 3));
    geometry.instanceCount = amount;

    let m = new THREE.LineBasicMaterial({
      color: 0x4488ff,
      transparent: true,
      onBeforeCompile: (shader) => {
        shader.uniforms = {
          ...shader.uniforms,
          ...uniforms,
        };

        shader.vertexShader = `
          uniform float time;
          uniform float maxHeight;
          
          attribute vec3 instPos;
          
          varying float colorTransition;
          varying vec3 vPos;
          ${shader.vertexShader}
        `.replace(
          `#include <begin_vertex>`,
          `#include <begin_vertex>
          
          float t = time;
          vec3 iPos = instPos;
          iPos.y = mod(maxHeight - instPos.y - t * 5., maxHeight);
          
          transformed.y *= 0.5;
          transformed += iPos;
          
          vPos = transformed;
          
          colorTransition = position.y;
          `
        );

        shader.fragmentShader = `
    uniform sampler2D depthData;
    uniform float maxHeight;
    
    varying float colorTransition;
    varying vec3 vPos;
    
    // 改进的深度检测函数，增加平滑处理
    float getSceneDepth(vec2 uv) {
        // 使用双线性过滤获取深度值
        float depthVal = 1. - texture2D(depthData, uv).r;
        // 转换为实际深度
        return depthVal * maxHeight;
    }
    
    ${shader.fragmentShader}
`.replace(
          `vec4 diffuseColor = vec4( diffuse, opacity );`,
          `
    // 标准化UV坐标到深度纹理范围
    vec2 depthUV = (vPos.xz + maxHeight * 0.5) / maxHeight;
    depthUV.y = 1. - depthUV.y;
    
    // 防止UV超出范围
    if (depthUV.x < 0.0 || depthUV.x > 1.0 || depthUV.y < 0.0 || depthUV.y > 1.0) {
        discard;
    }
    
    // 获取场景深度，平滑处理深度值
    float actualDepth = getSceneDepth(depthUV);
    float prevDepth = getSceneDepth(depthUV - vec2(0.001, 0.0));
    float nextDepth = getSceneDepth(depthUV + vec2(0.001, 0.0));
    actualDepth = (actualDepth + prevDepth + nextDepth) / 3.0;
    
    // 如果雨滴位置低于场景深度，则丢弃
    if(vPos.y < actualDepth) discard;
    
    float trns = 1. - colorTransition;
    
    // 计算雨滴与场景表面的距离
    float distVal = smoothstep(3.0, 0.0, vPos.y - actualDepth);
    vec3 col = mix(diffuse, vec3(0.9), distVal); // 距离越近，颜色越白
    vec4 diffuseColor = vec4( mix(col, col + 0.1, pow(trns, 16.0)), (opacity * (0.25 + 0.75 * distVal)) * trns );
    `
        );
      },
    });
    super(geometry, m);
    this.size = size;
    this.initialAmount = amount;
    this.currentAmount = amount;
    this.geometry = geometry;
    this.particlePositions = particlePositions;
    this.frustumCulled = false;
  }

  // 生成粒子位置数据
  generateParticlePositions(amount) {
    let v = new THREE.Vector3();
    return Array.from({ length: amount }, () => {
      v.random().subScalar(0.5);
      v.y += 0.5;
      v.multiply(this.size);
      return [...v];
    });
  }

  // 动态更新雨滴数量
  updateRainCount(newCount) {
    // 限制最小和最大雨滴数量 : [100, 100000]
    newCount = Math.max(100, Math.min(200000, newCount));

    if (newCount === this.currentAmount) return;

    this.currentAmount = newCount;

    // 如果新数量小于初始数量，只需调整instanceCount
    if (newCount <= this.initialAmount) {
      this.geometry.instanceCount = newCount;
      return;
    }

    // 如果新数量大于初始数量，需要重新生成位置数据
    const positions = this.generateParticlePositions(newCount);

    // 更新几何体属性
    const instPosAttr = this.geometry.attributes.instPos;

    // 如果需要更大的缓冲区，创建新的
    if (positions.length > instPosAttr.count) {
      this.geometry.setAttribute('instPos', new THREE.InstancedBufferAttribute(new Float32Array(positions.flat()), 3));
    } else {
      const array = instPosAttr.array;
      const newData = new Float32Array(positions.flat());
      array.set(newData);
      instPosAttr.needsUpdate = true;
    }

    this.geometry.instanceCount = newCount;
  }
}

/**
 * @typedef {[number, number, number]} RainBoxSize
 *
 * @typedef {object} RainOption
 * @property {import('three').WebGLRenderer} renderer
 * @property {number} [rainCount] 雨滴数量
 * @property {number} [maxHeight] 雨盒最大高度
 * @property {import('three').Vector3} [boxSize] 雨盒尺寸
 * @property {number} [rainSpeed] 雨滴下落速度
 *
 */
/**
 * TODO: maxHeight, X和Z轴长度，雨滴数量均可以变化
 * 雨天效果在演示模式时使用，此时Helper相关的应该禁用
 * 最好支持动态修改雨滴数量
 * @param {RainOption} option
 * @returns {{rain: Rain, renderRain: ({renderer: import('three').WebGLRenderer,scene}) => void}}
 */
export function createRainObject(option) {
  const { renderer, maxHeight = 20, boxSize = new THREE.Vector3(20, 20, 20), rainCount = 10000 } = option;
  // 启用时必须设置为true，此时会与viewHelper冲突
  renderer.autoClear = true;
  const boxWidth = boxSize.x;
  const boxHeight = boxSize.z;
  const boxDepth = maxHeight || boxSize.y;
  // 初始最大高度
  let depthData = new DepthData(1024, { width: boxWidth, height: boxHeight, depth: boxDepth });

  // 全局参数
  let gu = {
    time: { value: 0 },
    depthData: { value: depthData.depthTexture },
    maxHeight: { value: maxHeight },
  };

  // 创建雨效果
  const rain = new Rain(boxSize, rainCount, gu);

  // 添加函数用于动态修改maxHeight
  let clock = new THREE.Clock();
  let t = 0;

  const renderRain = (renderOption) => {
    let dt = clock.getDelta();
    t += dt;
    gu.time.value = t;
    depthData.update(renderOption);
  };
  return {
    rain,
    renderRain,
  };
}
